home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue54 / Persist / tiListView.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-01-07  |  12.9 KB  |  477 lines

  1. { * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2.   Purpose: TtiListView component to 'browse' a TList of TPersistent(s)
  3.  
  4.   Revision History:
  5.   Oct 1999, PWH, Created
  6.  
  7. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * }
  8.  
  9. {
  10.   ToDo 4-cListView:
  11.        a) Dynamiclaly size cols
  12.        b) Col heading even when no data (How do I do this?)
  13. }
  14.  
  15. unit tiListView ;
  16.  
  17. interface
  18. uses
  19.   ComCtrls
  20.   ,Classes
  21.   ,TypInfo
  22.   ,Controls
  23.   ,Menus
  24.   ;
  25.  
  26.  
  27. const
  28.   // Type kinds for use with tiGetProperty
  29.   // All string type properties
  30.   ctkString = [ tkChar, tkString, tkWChar, tkLString, tkWString ] ;
  31.   // Integer type properties
  32.   ctkInt    = [ tkInteger, tkInt64 ] ;
  33.   // Float type properties
  34.   ctkFloat  = [ tkFloat ] ;
  35.   // All simple types (string, int, float)
  36.   ctkSimple = ctkString + ctkInt + ctkFloat ;
  37.  
  38. type
  39.  
  40.   { ToDo 1 -ctiListView: Create ability to set col headings }
  41.   { ToDo 1 -ctiListView: Modify so compatable with tiListViewPlus. Main issue: The Filtering }
  42.  
  43.   TtiLVOnFilterDataEvent = procedure( pData : TPersistent ; var pbInclude : boolean ) of object ;
  44.  
  45.   //----------------------------------------------------------------------------
  46.   TtiListView = class( TCustomListView )
  47.   private
  48.     FData : TList ;
  49.     FCols : TStringList ;
  50.     FPopupMenu : TPopupMenu ;
  51.     FpmiEdit   : TMenuItem ;
  52.     FpmiNew    : TMenuItem ;
  53.     FpmiDelete : TMenuItem ;
  54.  
  55.  
  56.     FOnEdit : TNotifyEvent ;
  57.     FOnNew: TNotifyEvent;
  58.     FOnDelete: TNotifyEvent;
  59.     FOnFilterData : TtiLVOnFilterDataEvent ;
  60.  
  61.     procedure GetPropertyNames( pPersistent : TObject ;
  62.                                 pSL : TStringList ;
  63.                                 pPropFilter : TTypeKinds = ctkSimple ) ;
  64.  
  65.     procedure OnGetRowData(Sender: TObject; Item: TListItem);
  66.     // This is necessary when implementing OnGetFont and OnGetImage
  67.     //procedure DoOnCustomDrawItem( Sender: TCustomListView;
  68.     //                              Item: TListItem;
  69.     //                              State: TCustomDrawState;
  70.     //                              var DefaultDraw: Boolean ) ;
  71.     procedure SetData(const Value: TList);
  72.     function  GetColAlignment( psColName : string ) : TAlignment ;
  73.     function  GetColWidth( const psCol : string ) : integer ;
  74.  
  75.     procedure pmiEditOnClick( sender : TObject ) ;
  76.     procedure pmiDeleteOnClick( sender : TObject ) ;
  77.     procedure pmiNewOnClick( sender : TObject ) ;
  78.  
  79.   protected
  80.     procedure ApplyCols ;
  81.   published
  82.  
  83.     property    Align ;
  84.     property    Anchors ;
  85.     property    Items ;
  86.     property    MultiSelect ;
  87.     property    OnChange ;
  88.     property    OnChanging ;
  89.     property    OnColumnClick ;
  90.     property    SmallImages ;
  91.     property    ViewStyle;
  92.     property    RowSelect ;
  93.  
  94.     // These three properties are needed for drag-and-drop
  95.     property    OnDragOver  ;
  96.     property    OnDragDrop  ;
  97.     property    OnMouseDown ;
  98.  
  99.     property    Data : TList read FData write SetData ;
  100.  
  101.     property    OnEdit   : TNotifyEvent read FOnEdit   write FOnEdit ;
  102.     property    OnNew    : TNotifyEvent read FOnNew    write FOnNew ;
  103.     property    OnDelete : TNotifyEvent read FOnDelete write FOnDelete ;
  104.     property    OnFilterData : TtiLVOnFilterDataEvent
  105.                 read  FOnFilterData
  106.                 write FOnFilterData ;
  107.  
  108.  
  109.   public
  110.     constructor Create( owner : TComponent ) ; override ;
  111.     destructor  Destroy ; override ;
  112.     procedure   Refresh ; reintroduce ;
  113.  
  114.   end ;
  115.  
  116. implementation
  117. uses
  118.    SysUtils
  119. //   ,tiUtils   // for debugging
  120.    ;
  121.  
  122. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  123. // *
  124. // *  TtiListView
  125. // *
  126. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  127. constructor TtiListView.create(owner: TComponent);
  128. begin
  129.   inherited create( owner ) ;
  130.  
  131.   FCols := TStringList.Create ;
  132.  
  133.   // Configure the list view
  134.   ReadOnly         := true ;
  135.   HideSelection    := false ;
  136.   ViewStyle        := vsReport ;
  137.   RowSelect        := true ;
  138.   // For some reason, double clicking, then showing a modal dialog on a
  139.   // ListView that has drag&drop enabled will trigger the drag!
  140.   // So no double clicking.
  141.   // OnDblClick       := DoDoubleClick ;
  142.   
  143.   // Create the popup menu
  144.   FPopupMenu := TPopupMenu.Create( self ) ;
  145.   PopupMenu  := FPopupMenu ;
  146.  
  147.   // Create select columns menu item
  148.   FpmiEdit          := TMenuItem.Create( self ) ;
  149.   FpmiEdit.Caption  := '&Edit' ;
  150.   FpmiEdit.OnClick  := pmiEditOnClick ;
  151.   FpmiEdit.Shortcut := TextToShortcut( 'Enter' ) ;
  152.   FPopupMenu.Items.Add( FpmiEdit ) ;
  153.  
  154.   FpmiNew          := TMenuItem.Create( self ) ;
  155.   FpmiNew.Caption  := '&New' ;
  156.   FpmiNew.OnClick  := pmiNewOnClick ;
  157.   FpmiNew.Shortcut := TextToShortcut( 'Ins' ) ;
  158.   FPopupMenu.Items.Add( FpmiNew ) ;
  159.  
  160.   FpmiDelete        := TMenuItem.Create( self ) ;
  161.   FpmiDelete.Caption  := '&Delete' ;
  162.   FpmiDelete.OnClick  := pmiDeleteOnClick ;
  163.   FpmiDelete.Shortcut := TextToShortcut( 'Del' ) ;
  164.   FPopupMenu.Items.Add( FpmiDelete ) ;
  165.  
  166.  
  167. end;
  168.  
  169. destructor TtiListView.Destroy ;
  170. begin
  171.   FCols.Free ;
  172.   inherited ;
  173. end ;
  174.  
  175. //------------------------------------------------------------------------------
  176. procedure TtiListView.OnGetRowData(Sender: TObject; Item: TListItem);
  177. var
  178.   i         : integer ;
  179.   ls        : string ;
  180.   lsCol     : string ;
  181.   lData     : TPersistent ;
  182.   liIndex   : integer ;
  183.   lbInclude : boolean ;
  184. begin
  185.   inherited ;
  186.  
  187.   if FData = nil then
  188.     exit ; //==>
  189.  
  190.   Assert( Item <> nil, 'Nil list item' ) ;
  191.  
  192.   Assert( FData <> nil, 'Data property not set' ) ;
  193.   Assert( TObject( FData.Items[Item.Index]) is TPersistent,
  194.           'Data object is not TPersistent' ) ;
  195.  
  196.   // Scan through the items in the dataList and allow for items which
  197.   // will not be shown.
  198.   // For a long list, this will be very slow.
  199.   if Assigned( FOnFilterData ) then begin
  200.     liIndex := -1 ;
  201.     i := -1 ;
  202.     while liIndex <> Item.Index do begin
  203.       inc( i ) ;
  204.       lbInclude := true ;
  205.       lData := TObject( FData.Items[i]) as TPersistent ;
  206.       FOnFilterData( lData, lbInclude ) ;
  207.       if lbInclude then
  208.         inc( liIndex ) ;
  209.     end ;
  210.     liIndex := i ;
  211.   end else
  212.     liIndex := Item.Index ;
  213.  
  214.   if liIndex < 0 then
  215.     exit ; //==>
  216.  
  217.   lData := TObject( FData.Items[ liIndex ]) as TPersistent ;
  218.  
  219.   // The first col must be assinged to the the caption property
  220.   if FCols.Count > 0 then begin
  221.     lsCol := FCols.Strings[0] ;
  222.     if lsCol <> '' then begin
  223.       try
  224.         ls := GetPropValue( lData, lsCol ) ;
  225.       except
  226.         on e:exception do begin
  227.           raise exception.create( 'Error reading property <' +
  228.                                   lsCol + '> ' + e.message +
  229.                                   ' Called in TtiListView.OnGetRowData' ) ;
  230.           ls := 'N/A' ;
  231.         end ;
  232.       end ;
  233.       Item.Caption := ls  ;
  234.     end ;
  235.   end ;
  236.  
  237.   // Cols 1.. are added to SubItems
  238.   for i := 1 to FCols.Count - 1 do begin
  239.     lsCol := FCols.Strings[i] ;
  240.     if lsCol <> '' then begin
  241.       try
  242.         ls := GetPropValue( lData, lsCol ) ;
  243.       except
  244.         on e:exception do begin
  245.           raise exception.create( 'Error reading property <' +
  246.                                   lsCol + '> ' + e.message +
  247.                                   ' Called in TtiListView.OnGetRowData' ) ;
  248.           ls := 'N/A' ;
  249.         end ;
  250.       end ;
  251.  
  252.       Item.SubItems.Add( ls ) ;
  253.       Item.Data := lData ;
  254.     end ;
  255.   end ;
  256.  
  257. {
  258.   // Copy this back from TtiListViewPlus
  259.   if Assigned( FOnGetImageIndex ) and
  260.      Assigned( SmallImages ) then begin
  261.     liImageIndex := -1 ;
  262.     FOnGetImageIndex( lData, liImageIndex ) ;
  263.     Item.ImageIndex := liImageIndex ;
  264.   end ;
  265. }
  266.  
  267. end;
  268.  
  269. procedure TtiListView.SetData(const Value: TList ) ;
  270. begin
  271.  
  272.   if FData = Value then
  273.     exit ; //==>
  274.  
  275.   FData := Value ;
  276.  
  277.   if (FData = nil) or
  278.      (FData.Count<1) then begin
  279.     Items.Count := 0 ;
  280.     OwnerData := false ;
  281.     OnData    := nil ;
  282.     exit ; //==>
  283.   end;
  284.  
  285.   // Read the abailable cols from the first element of the object list
  286.   // into FLVConfig.ColsAvailable
  287. {
  288.   if ( Data <> nil ) and
  289.      ( Data.Count > 0 ) and
  290.      ( TObject( Data.Items[0] ) is TPersistent ) then begin
  291.     // Read all properties
  292.     GetPropertyNames( ( TObject( Data.Items[0] ) as TPersistent),
  293.                         FCols ) ;
  294.  
  295.   end ;
  296. }
  297.  
  298. //  OnData    := OnGetRowData ;
  299. //  OwnerData := true ;
  300.  
  301. //  Items.Count := 0 ;
  302.   Refresh ;
  303.  
  304. end ;
  305.  
  306.  
  307. procedure TtiListView.refresh ;
  308. var
  309.   i : integer ;
  310.   liCount : integer ;
  311.   lData : TPersistent ;
  312.   lbInclude : boolean ;
  313. begin
  314.  
  315.   { ToDo 1 -cListView: Save the position of the cursor before refreshing }
  316.   OnData    := nil ;
  317.   OwnerData := false ;
  318.   Items.Clear ;
  319.  
  320.   liCount := 0 ;
  321.   for i := 0 to FData.Count - 1 do begin
  322.     lData := TObject( FData.Items[i] ) as TPersistent ;
  323.     if Assigned( FOnFilterData ) then begin
  324.       lbInclude := true ;
  325.       FOnFilterData( lData, lbInclude ) ;
  326.       if lbInclude then
  327.         inc( liCount ) ;
  328.     end else
  329.       inc( liCount ) ;
  330.   end ;
  331.  
  332.   if liCount > 0 then begin
  333.     // The order of these three assignments is important.
  334.     // Do not change the order.
  335.     // Must set OwnerData, ApplyCols, Items.Count, OnData order.
  336.     OwnerData   := true ;
  337.     ApplyCols ;
  338.     Items.Count := liCount ;
  339.     OnData      := OnGetRowData ;
  340.   end ;
  341.  
  342.   inherited Refresh ;
  343. end ;
  344.  
  345. procedure TtiListView.ApplyCols ;
  346. var
  347.   i : integer;
  348.   lsColName : string ;
  349.   lAlignment : TAlignment ;
  350. begin
  351.  
  352.   Visible := false ;
  353.   Cursor := crHourGlass ;
  354.   try
  355.  
  356.     // Read all the available cols into the col string list
  357.     if ( Data <> nil ) and
  358.        ( Data.Count > 0 ) and
  359.        ( TObject( Data.Items[0] ) is TPersistent ) then begin
  360.       // Read all properties
  361.       GetPropertyNames( ( TObject( Data.Items[0] ) as TPersistent),
  362.                           FCols ) ;
  363.      end ;
  364.  
  365.     Columns.Clear ;
  366.  
  367.     // Note: Col[0] will always be left justified, so it may be necessary
  368.     //       to add a dummy col[0]
  369.     // Read the column headings
  370.     for i := 0 to FCols.count - 1 do begin
  371.       lsColName := FCols.Strings[i] ;
  372.       if ( lsColName <> '' ) then begin
  373.         try
  374.           lAlignment := GetColAlignment( lsColName ) ;
  375.           Columns.Add ;
  376.           Columns[Columns.Count-1].Caption   := lsColName ;
  377.           Columns[Columns.Count-1].Width     := GetColWidth( lsColName ) ;
  378.           Columns[Columns.Count-1].Alignment := lAlignment ;
  379.         except end ;
  380.       end ;
  381.     end ;
  382.   finally
  383.     Cursor  := crDefault ;
  384.     Visible := true ;
  385.   end ;
  386. end;
  387.  
  388. function TtiListView.GetColWidth( const psCol : string ) : integer ;
  389. begin
  390.   result := 100 ;
  391. end ;
  392.  
  393. // Derive the column alignment from the col's data type.
  394. // Note: Col[0] will always be left justified.
  395. //------------------------------------------------------------------------------
  396. function TtiListView.GetColAlignment( psColName : string ) : TAlignment ;
  397. var
  398.   lPropType : TTypeKind ;
  399. begin
  400.   result := taLeftJustify ;
  401.   if ( Data = nil ) or
  402.      ( Data.Count = 0 ) or
  403.      ( Data.Items[0] = nil ) then
  404.     exit ; //==>
  405.  
  406.   Assert( TObject(Data.Items[0]) is TPersistent,
  407.             'Object in list passed as data to TtiListView not TPersistent' ) ;
  408.  
  409.   lPropType := PropType( ( TObject(Data.Items[0]) as TPersistent ), psColName ) ;
  410.   if lPropType in [ tkInteger, tkInt64, tkFloat ] then
  411.     result := taRightJustify ;
  412.  
  413. end ;
  414.  
  415. {
  416. procedure TtiListView.DoOnCustomDrawItem(Sender: TCustomListView;
  417.   Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
  418. begin
  419.   if Assigned( FOnGetFont ) then
  420.     FOnGetFont( ( TObject( Item.Data ) as TPersistent ),
  421.                 Canvas.Font ) ;
  422.   DefaultDraw := true ;
  423. end;
  424. }
  425.  
  426. procedure TtiListView.GetPropertyNames(pPersistent: TObject;pSL: TStringList; pPropFilter: TTypeKinds);
  427. var
  428.   lCount : integer ;
  429.   lSize  : integer ;
  430.   lList  : PPropList ;
  431.   i : integer ;
  432.   lPropFilter : TTypeKinds ;
  433. begin
  434.   Assert( pPersistent <> nil, 'pPersistent not assigned.' ) ;
  435.   Assert( pSL <> nil, 'pSL not assigned.' ) ;
  436.   lPropFilter := pPropFilter ;
  437.   pSL.Clear ;
  438.   lCount := GetPropList(pPersistent.ClassInfo, lPropFilter, nil);
  439.   lSize := lCount * SizeOf(Pointer);
  440.   GetMem(lList, lSize);
  441.   try
  442.     GetPropList(pPersistent.ClassInfo, lPropFilter, lList);
  443.     for i := 0 to lcount - 1 do
  444.       psl.add( lList[i].Name ) ;
  445.   finally
  446.     FreeMem( lList, lSize ) ;
  447.   end ;
  448. end;
  449.  
  450. procedure TtiListView.pmiDeleteOnClick(sender: TObject);
  451. begin
  452.   if ( Selected <> nil ) and
  453.      ( Assigned( FOnDelete )) then
  454.     FOnDelete( Selected ) ;
  455. end;
  456.  
  457. procedure TtiListView.pmiEditOnClick(sender: TObject);
  458. begin
  459.   if ( Selected <> nil ) and
  460.      ( Assigned( FOnEdit )) then begin
  461.     FOnEdit( Selected ) ;
  462.     SetFocus ;
  463.   end ;
  464. end;
  465.  
  466. procedure TtiListView.pmiNewOnClick(sender: TObject);
  467. begin
  468.   if ( Assigned( FOnNew )) then begin
  469.     FOnNew( nil ) ;
  470.     SetFocus ;
  471.   end ;
  472. end;
  473.  
  474. end.
  475.  
  476.  
  477.